﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace RatingAuthor
{
  public class SiDatabase
  {
    private string _fileName;

    public GenresLookup GenresLookup { get; private set; }

    public Dictionary<AuthorUrl, Author> AuthorsByUrl { get; private set; }

    public SiDatabase()
    {
      AuthorsByUrl = new Dictionary<AuthorUrl, Author>();
      GenresLookup = new GenresLookup();
    }
    private SiDatabase(Dictionary<AuthorUrl, Author> authorsByUrl)
    {
      AuthorsByUrl = authorsByUrl;
      GenresLookup = new GenresLookup();
    }

    public void ResetFlags()
    {
      foreach (var a in AuthorsByUrl.Values)
      {
        a.IsNew = false;
        a.IsUpdated = false;
        foreach (var b in a.BooksByUrl.Values)
        {
          b.IsNew = false;
          b.IsUpdated = false;
        }
      }
    }
    public void ApplyUpdate(IEnumerable<BookRecord> records)
    {
      foreach (var r in records.Where(r => r.IsValid))
      {
        Author author;
        if (!AuthorsByUrl.TryGetValue(r.AuthorUrl, out author))
          AuthorsByUrl[r.AuthorUrl] = author = new Author(r);

        author.UpdateBook(r, GenresLookup);
      }
    }
    public Statistics GetStatistics()
    {
      return new Statistics
               {
                 NewAuthors = AuthorsByUrl.Values.Count(a => a.IsNew),
                 AuthorsUpdated = AuthorsByUrl.Values.Count(a => a.IsUpdated),
                 NewBooks = AuthorsByUrl.Values.Sum(a => a.BooksByUrl.Values.Count(b => b.IsNew)),
                 BooksUpdated = AuthorsByUrl.Values.Sum(a => a.BooksByUrl.Values.Count(b => b.IsUpdated))
               };
    }
    public Statistics Update(IParser parser, Action<int, int> progress)
    {
      var cache = new PagesCache(parser.Name);
      var b = new IndexedPagesBehaviour(parser.IndexedTamplateUrl, cache.IsCached);
      var downloadedFiles = new MultithreadWorker<Page>(b.Download);
      var cachedPages = downloadedFiles.Process(f => f.FixEncodingAndCache(cache));

      foreach (var cachedFile in cache.CachedFiles)
        cachedPages.Add(cachedFile);

      ResetFlags();

      cachedPages
        .Process(f => parser.Parse(f))
        .Process(f => progress(f.PageIndex, f.OveralPagesCount))
        .SingleThreadedProcess(f => ApplyUpdate(f.Records));

      return GetStatistics();
    }
    public static SiDatabase LoadIfExists(string path, Action<double> progress)
    {
      var authorsByUrl = new Dictionary<AuthorUrl, Author>();
      if (!File.Exists(path)) return new SiDatabase(authorsByUrl);
      using (var file = new BinaryReader(File.OpenRead(path)))
      {
        var genresLookup = GenresLookup.Load(file);
        var authorsCount = file.ReadInt32();
        for (int i = 0; i < authorsCount; i++)
        {
          var author = new Author
          {
            Url = new AuthorUrl(file.ReadChar(), file.ReadString()),
            Name = file.ReadString()
          };
          var booksCount = file.ReadInt32();
          for (int j = 0; j < booksCount; j++)
          {
            var book = new Book
            {
              Url = file.ReadString(),
              Title = file.ReadString(),
              MarksCount = file.ReadInt32(),
              Mark = file.ReadDouble(),
              Size = file.ReadInt32(),
              CommentsCount = file.ReadInt32(),
              Ganres = file.ReadInt64(),
            };

            author.BooksByUrl[book.Url] = book;
            book.Author = author;
          }
          authorsByUrl[author.Url] = author;
          if (i % 100 == 0)
          {
            progress(i / (double)authorsCount);
          }
        }
        Console.WriteLine();
        return new SiDatabase(authorsByUrl)
                 {
                   _fileName = path,
                   GenresLookup = genresLookup,
                 };
      }

    }
    public void Save(Action<double> progress)
    {
      Save(_fileName, progress);
    }
    public void Save(string path, Action<double> progress)
    {
      _fileName = path;
      using (var file = new BinaryWriter(File.Create(path)))
      {
        GenresLookup.Save(file);
        file.Write(AuthorsByUrl.Count);
        int counter = 0;
        foreach (var a in AuthorsByUrl.Values)
        {
          file.Write(a.Url.FirstLetter);
          file.Write(a.Url.Value);
          file.Write(a.Name);
          file.Write(a.BooksByUrl.Count);

          foreach (var b in a.BooksByUrl.Values)
          {
            file.Write(b.Url);
            file.Write(b.Title);
            file.Write(b.MarksCount);
            file.Write(b.Mark);
            file.Write(b.Size);
            file.Write(b.CommentsCount);
            file.Write(b.Ganres);
          }
          if (++counter % 100 == 0)
          {
            progress(counter / (double)AuthorsByUrl.Count);
          }
        }
      }
    }
  }
}